/*-------------------------------------------------------------------------
 *
 * variable.c
 *		处理专用 SET 变量的例程。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/variable.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <ctype.h>

#include "access/htup_details.h"
#include "access/parallel.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/pg_authid.h"
#include "commands/variable.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
#include "utils/varlena.h"

/*
 * 日期样式
 */

/*
 * check_datestyle: GUC 检查钩子用于日期样式
 */
bool check_datestyle(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	int			fc_newDateStyle = DateStyle;
	int			fc_newDateOrder = DateOrder;
	bool		fc_have_style = false;
	bool		fc_have_order = false;
	bool		fc_ok = true;
	char	   *fc_rawstring;
	int		   *fc_myextra;
	char	   *fc_result;
	List	   *fc_elemlist;
	ListCell   *fc_l;

	/* 需要可修改的字符串副本 */
	fc_rawstring = pstrdup(*fc_newval);

	/* 将字符串解析为标识符列表 */
	if (!SplitIdentifierString(fc_rawstring, ',', &fc_elemlist))
	{
		/* 列表中的语法错误 */
		GUC_check_errdetail("List syntax is invalid.");
		pfree(fc_rawstring);
		list_free(fc_elemlist);
		return false;
	}

	foreach(fc_l, fc_elemlist)
	{
		char	   *fc_tok = (char *) lfirst(fc_l);

		/* 哦，应该写一个表驱动版本 -- mjl */

		if (pg_strcasecmp(fc_tok, "ISO") == 0)
		{
			if (fc_have_style && fc_newDateStyle != USE_ISO_DATES)
				fc_ok = false;		/* 冲突样式 */
			fc_newDateStyle = USE_ISO_DATES;
			fc_have_style = true;
		}
		else if (pg_strcasecmp(fc_tok, "SQL") == 0)
		{
			if (fc_have_style && fc_newDateStyle != USE_SQL_DATES)
				fc_ok = false;		/* 冲突样式 */
			fc_newDateStyle = USE_SQL_DATES;
			fc_have_style = true;
		}
		else if (pg_strncasecmp(fc_tok, "POSTGRES", 8) == 0)
		{
			if (fc_have_style && fc_newDateStyle != USE_POSTGRES_DATES)
				fc_ok = false;		/* 冲突样式 */
			fc_newDateStyle = USE_POSTGRES_DATES;
			fc_have_style = true;
		}
		else if (pg_strcasecmp(fc_tok, "GERMAN") == 0)
		{
			if (fc_have_style && fc_newDateStyle != USE_GERMAN_DATES)
				fc_ok = false;		/* 冲突样式 */
			fc_newDateStyle = USE_GERMAN_DATES;
			fc_have_style = true;
			/* 德国样式还设置了 DMY，除非被明确覆盖 */
			if (!fc_have_order)
				fc_newDateOrder = DATEORDER_DMY;
		}
		else if (pg_strcasecmp(fc_tok, "YMD") == 0)
		{
			if (fc_have_order && fc_newDateOrder != DATEORDER_YMD)
				fc_ok = false;		/* 冲突顺序 */
			fc_newDateOrder = DATEORDER_YMD;
			fc_have_order = true;
		}
		else if (pg_strcasecmp(fc_tok, "DMY") == 0 ||
				 pg_strncasecmp(fc_tok, "EURO", 4) == 0)
		{
			if (fc_have_order && fc_newDateOrder != DATEORDER_DMY)
				fc_ok = false;		/* 冲突顺序 */
			fc_newDateOrder = DATEORDER_DMY;
			fc_have_order = true;
		}
		else if (pg_strcasecmp(fc_tok, "MDY") == 0 ||
				 pg_strcasecmp(fc_tok, "US") == 0 ||
				 pg_strncasecmp(fc_tok, "NONEURO", 7) == 0)
		{
			if (fc_have_order && fc_newDateOrder != DATEORDER_MDY)
				fc_ok = false;		/* 冲突顺序 */
			fc_newDateOrder = DATEORDER_MDY;
			fc_have_order = true;
		}
		else if (pg_strcasecmp(fc_tok, "DEFAULT") == 0)
		{
			/*
			 * 获取当前的默认状态最简单的方法是从 guc.c 中获取
			 * 默认字符串并递归解析它。
			 *
			 * 我们不能简单地 "return check_datestyle(...)"，因为我们需要
			 * 处理像 "DEFAULT, ISO" 这样的构造。
			 */
			char	   *fc_subval;
			void	   *fc_subextra = NULL;

			fc_subval = strdup(GetConfigOptionResetString("datestyle"));
			if (!fc_subval)
			{
				fc_ok = false;
				break;
			}
			if (!check_datestyle(&fc_subval, &fc_subextra, fc_source))
			{
				free(fc_subval);
				fc_ok = false;
				break;
			}
			fc_myextra = (int *) fc_subextra;
			if (!fc_have_style)
				fc_newDateStyle = fc_myextra[0];
			if (!fc_have_order)
				fc_newDateOrder = fc_myextra[1];
			free(fc_subval);
			free(fc_subextra);
		}
		else
		{
			GUC_check_errdetail("Unrecognized key word: \"%s\".", fc_tok);
			pfree(fc_rawstring);
			list_free(fc_elemlist);
			return false;
		}
	}

	pfree(fc_rawstring);
	list_free(fc_elemlist);

	if (!fc_ok)
	{
		GUC_check_errdetail("Conflicting \"datestyle\" specifications.");
		return false;
	}

	/*
	 * 准备返回的规范字符串。GUC 需要它是动态分配的。
	 */
	fc_result = (char *) malloc(32);
	if (!fc_result)
		return false;

	switch (fc_newDateStyle)
	{
		case USE_ISO_DATES:
			strcpy(fc_result, "ISO");
			break;
		case USE_SQL_DATES:
			strcpy(fc_result, "SQL");
			break;
		case USE_GERMAN_DATES:
			strcpy(fc_result, "German");
			break;
		default:
			strcpy(fc_result, "Postgres");
			break;
	}
	switch (fc_newDateOrder)
	{
		case DATEORDER_YMD:
			strcat(fc_result, ", YMD");
			break;
		case DATEORDER_DMY:
			strcat(fc_result, ", DMY");
			break;
		default:
			strcat(fc_result, ", MDY");
			break;
	}

	free(*fc_newval);
	*fc_newval = fc_result;

	/*
	 * 设置 assign_datestyle 实际使用的 "extra" 结构。
	 */
	fc_myextra = (int *) malloc(2 * sizeof(int));
	if (!fc_myextra)
		return false;
	fc_myextra[0] = fc_newDateStyle;
	fc_myextra[1] = fc_newDateOrder;
	*fc_extra = (void *) fc_myextra;

	return true;
}

/*
 * assign_datestyle: GUC 分配钩子用于日期样式
 */
void assign_datestyle(const char *fc_newval, void *fc_extra)
{
	int		   *fc_myextra = (int *) fc_extra;

	DateStyle = fc_myextra[0];
	DateOrder = fc_myextra[1];
}


/*
 * 时区
 */

/*
 * check_timezone: GUC 检查钩子用于时区
 */
bool check_timezone(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	pg_tz	   *fc_new_tz;
	long		fc_gmtoffset;
	char	   *fc_endptr;
	double		fc_hours;

	if (pg_strncasecmp(*fc_newval, "interval", 8) == 0)
	{
		/*
		 * 支持 INTERVAL 'foo'。这是为了符合 SQL 规范，而不是因为它
		 * 具有任何实际的现实用途。
		 */
		const char *fc_valueptr = *fc_newval;
		char	   *fc_val;
		Interval   *fc_interval;

		fc_valueptr += 8;
		while (isspace((unsigned char) *fc_valueptr))
			fc_valueptr++;
		if (*fc_valueptr++ != '\'')
			return false;
		fc_val = pstrdup(fc_valueptr);
		/* 检查并移除尾随引号 */
		fc_endptr = strchr(fc_val, '\'');
		if (!fc_endptr || fc_endptr[1] != '\0')
		{
			pfree(fc_val);
			return false;
		}
		*fc_endptr = '\0';

		/*
		 * 尝试解析它。XXX 无效的间隔格式将导致 ereport(ERROR)，这对于 GUC
		 * 是不可取的。我们尽力在 flatten_set_variable_args 中对此进行了防护，
		 * 但从 postgresql.conf 中传入的字符串可能包含任何内容。
		 */
		fc_interval = DatumGetIntervalP(DirectFunctionCall3(interval_in,
														 CStringGetDatum(fc_val),
														 ObjectIdGetDatum(InvalidOid),
														 Int32GetDatum(-1)));

		pfree(fc_val);
		if (fc_interval->month != 0)
		{
			GUC_check_errdetail("Cannot specify months in time zone interval.");
			pfree(fc_interval);
			return false;
		}
		if (fc_interval->day != 0)
		{
			GUC_check_errdetail("Cannot specify days in time zone interval.");
			pfree(fc_interval);
			return false;
		}

		/* 在这里我们从 SQL 转换为 Unix 符号约定 */
		fc_gmtoffset = -(fc_interval->time / USECS_PER_SEC);
		fc_new_tz = pg_tzset_offset(fc_gmtoffset);

		pfree(fc_interval);
	}
	else
	{
		/*
		 * 尝试作为一个数字小时（可能是分数）。
		 */
		fc_hours = strtod(*fc_newval, &fc_endptr);
		if (fc_endptr != *fc_newval && *fc_endptr == '\0')
		{
			/* 在这里我们从 SQL 转换为 Unix 符号约定 */
			fc_gmtoffset = -fc_hours * SECS_PER_HOUR;
			fc_new_tz = pg_tzset_offset(fc_gmtoffset);
		}
		else
		{
			/*
			 * 否则假设它是一个时区名称，并尝试加载它。
			 */
			fc_new_tz = pg_tzset(*fc_newval);

			if (!fc_new_tz)
			{
				/* 在这里似乎没有什么重大价值可以在 errdetail 中显示 */
				return false;
			}

			if (!pg_tz_acceptable(fc_new_tz))
			{
				GUC_check_errmsg("time zone \"%s\" appears to use leap seconds",
								 *fc_newval);
				GUC_check_errdetail("PostgreSQL does not support leap seconds.");
				return false;
			}
		}
	}

	/* 测试 pg_tzset_offset 中的失败，我们假设它超出范围 */
	if (!fc_new_tz)
	{
		GUC_check_errdetail("UTC timezone offset is out of range.");
		return false;
	}

	/*
	 * 将数据传回给 assign_timezone 使用
	 */
	*fc_extra = malloc(sizeof(pg_tz *));
	if (!*fc_extra)
		return false;
	*((pg_tz **) *fc_extra) = fc_new_tz;

	return true;
}

/*
 * assign_timezone: GUC 分配钩子用于时区
 */
void assign_timezone(const char *fc_newval, void *fc_extra)
{
	session_timezone = *((pg_tz **) fc_extra);
}

/*
 * show_timezone: GUC 显示钩子用于时区
 */
const char * show_timezone(void)
{
	const char *fc_tzn;

	/* 始终显示该区域的规范名称 */
	fc_tzn = pg_get_timezone_name(session_timezone);

	if (fc_tzn != NULL)
		return fc_tzn;

	return "unknown";
}


/*
 * LOG_TIMEZONE
 *
 * 对于 log_timezone，我们不支持基于间隔的方法来设置一个
 * 区域，这些方法仅为符合 SQL 规范，而并不是因为它们
 * 实际上有用。
 */

/*
 * check_log_timezone: GUC 检查钩子用于 log_timezone
 */
bool check_log_timezone(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	pg_tz	   *fc_new_tz;

	/*
	 * 假设它是一个时区名称，并尝试加载它。
	 */
	fc_new_tz = pg_tzset(*fc_newval);

	if (!fc_new_tz)
	{
		/* 在这里似乎没有什么重大价值可以在 errdetail 中显示 */
		return false;
	}

	if (!pg_tz_acceptable(fc_new_tz))
	{
		GUC_check_errmsg("time zone \"%s\" appears to use leap seconds",
						 *fc_newval);
		GUC_check_errdetail("PostgreSQL does not support leap seconds.");
		return false;
	}

	/*
	 * 将数据传回给 assign_log_timezone 使用
	 */
	*fc_extra = malloc(sizeof(pg_tz *));
	if (!*fc_extra)
		return false;
	*((pg_tz **) *fc_extra) = fc_new_tz;

	return true;
}

/*
 * assign_log_timezone: GUC 分配钩子用于 log_timezone
 */
void assign_log_timezone(const char *fc_newval, void *fc_extra)
{
	log_timezone = *((pg_tz **) fc_extra);
}

/*
 * show_log_timezone: GUC 显示钩子用于 log_timezone
 */
const char * show_log_timezone(void)
{
	const char *fc_tzn;

	/* 始终显示该区域的规范名称 */
	fc_tzn = pg_get_timezone_name(log_timezone);

	if (fc_tzn != NULL)
		return fc_tzn;

	return "unknown";
}


/*
 * 设置事务为只读和设置事务为读写
 *
 * 我们允许在任何时间进行幂等更改（r/w -> r/w 和 r/o -> r/o），
 * 我们也始终允许从读写更改为只读。然而，
 * 只读只能在尚未进行初始快照的顶层事务中更改为读写。
 * 在热备份中也无法执行此操作。
 *
 * 如果我们根本不在事务中，只需允许更改；因为没有意义，
 * 因为下一个 StartTransaction() 会重置 XactReadOnly。
 * IsTransactionState() 测试可以保护我们避免在共享内存不可访问的上下文中检查
 * RecoveryInProgress()。 
 * （同样，如果我们在并行工作者中恢复状态，只需允许
 * 更改。）
 */
bool check_transaction_read_only(bool *fc_newval, void **fc_extra, GucSource fc_source)
{
	if (*fc_newval == false && XactReadOnly && IsTransactionState() && !InitializingParallelWorker)
	{
		/* 不能在只读事务中切换到读写模式 */
		if (IsSubTransaction())
		{
			GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
			GUC_check_errmsg("cannot set transaction read-write mode inside a read-only transaction");
			return false;
		}
		/* 顶层事务在第一次快照后不能更改为读写。 */
		if (FirstSnapshotSet)
		{
			GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
			GUC_check_errmsg("transaction read-write mode must be set before any query");
			return false;
		}
		/* 恢复仍在活动状态时不能切换到读写模式 */
		if (RecoveryInProgress())
		{
			GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
			GUC_check_errmsg("cannot set transaction read-write mode during recovery");
			return false;
		}
	}

	return true;
}

/*
 * 设置事务隔离级别
 *
 * 我们在任何时间都允许幂等更改，但在其他情况下，这只能在尚未进行快照的顶层事务中更改。
 *
 * 与 check_transaction_read_only 中一样，如果不在事务中则允许。
 */
bool check_XactIsoLevel(int *fc_newval, void **fc_extra, GucSource fc_source)
{
	int			fc_newXactIsoLevel = *fc_newval;

	if (fc_newXactIsoLevel != XactIsoLevel && IsTransactionState())
	{
		if (FirstSnapshotSet)
		{
			GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
			GUC_check_errmsg("SET TRANSACTION ISOLATION LEVEL must be called before any query");
			return false;
		}
		/* 我们忽略子事务将其设置为现有值。 */
		if (IsSubTransaction())
		{
			GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
			GUC_check_errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction");
			return false;
		}
		/* 恢复仍在活动状态时不能切换到可序列化模式 */
		if (fc_newXactIsoLevel == XACT_SERIALIZABLE && RecoveryInProgress())
		{
			GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
			GUC_check_errmsg("cannot use serializable mode in a hot standby");
			GUC_check_errhint("You can use REPEATABLE READ instead.");
			return false;
		}
	}

	return true;
}

/*
 * 设置事务 [非] 可延迟
 */

bool check_transaction_deferrable(bool *fc_newval, void **fc_extra, GucSource fc_source)
{
	if (IsSubTransaction())
	{
		GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
		GUC_check_errmsg("SET TRANSACTION [NOT] DEFERRABLE cannot be called within a subtransaction");
		return false;
	}
	if (FirstSnapshotSet)
	{
		GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
		GUC_check_errmsg("SET TRANSACTION [NOT] DEFERRABLE must be called before any query");
		return false;
	}

	return true;
}

/*
 * 随机数种子
 *
 * 我们无法在错误时回滚随机序列，并且不希望配置文件的重新加载影响它，
 * 所以我们只希望交互式 SET SEED 命令来设置它。我们使用“额外”的存储
 * 以确保回滚不会尝试再次执行该操作。
 */

bool check_random_seed(double *fc_newval, void **fc_extra, GucSource fc_source)
{
	*fc_extra = malloc(sizeof(int));
	if (!*fc_extra)
		return false;
	/* 仅在值的来源是交互式 SET 时赋值 */
	*((int *) *fc_extra) = (fc_source >= PGC_S_INTERACTIVE);

	return true;
}

void assign_random_seed(double fc_newval, void *fc_extra)
{
	/* 对于 GUC 变量的任何设置，我们最多只会执行一次此操作 */
	if (*((int *) fc_extra))
		DirectFunctionCall1(setseed, Float8GetDatum(fc_newval));
	*((int *) fc_extra) = 0;
}

const char * show_random_seed(void)
{
	return "unavailable";
}


/*
 * 设置客户端编码
 */

bool check_client_encoding(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	int			fc_encoding;
	const char *fc_canonical_name;

	/* 按名称查找编码 */
	fc_encoding = pg_valid_client_encoding(*fc_newval);
	if (fc_encoding < 0)
		return false;

	/* 获取规范名称（无别名，统一大小写） */
	fc_canonical_name = pg_encoding_to_char(fc_encoding);

	/*
	 * 如果我们不在事务中，则 PrepareClientEncoding 将无法查找必要的转换过程。
	 * 如果我们仍在启动，则它无论如何都会返回“OK”，而 InitializeClientEncoding
	 * 将在初始化足够完成后修复问题。启动后，我们将失败。
	 * 这仅在有人尝试在 postgresql.conf 中更改 client_encoding 然后 SIGHUP 现有会话时发生。
	 * 任何情况下通过这种方式更改 client_encoding 似乎都是个坏主意，
	 * 所以我们不会特意去支持它。
	 *
	 * 注意：在 postmaster 或任何其他从未调用过
	 * InitializeClientEncoding 的进程中，PrepareClientEncoding 将始终成功，
	 * SetClientEncoding 也是；但它们不会做任何事情，这没关系。
	 */
	if (PrepareClientEncoding(fc_encoding) < 0)
	{
		if (IsTransactionState())
		{
			/* 必须是真正的无此转换问题 */
			GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
			GUC_check_errdetail("Conversion between %s and %s is not supported.",
								fc_canonical_name,
								GetDatabaseEncodingName());
		}
		else
		{
			/* 提供有用的投诉 */
			GUC_check_errdetail("Cannot change \"client_encoding\" now.");
		}
		return false;
	}

	/*
	 * 用编码的标准名称替换用户提供的字符串。
	 * 这消除了别名和大小写变体。
	 *
	 * XXX 尽管标准化在抽象上似乎是个好主意，但它
	 * 破坏了预9.1版本的JDBC驱动程序，这些驱动程序期望如果它们发送 "UNICODE"
	 * 作为client_encoding设置，则会以相同的方式读取。作为
	 * 权宜之计，如果字符串是 "UNICODE"，则不要替换它。
	 * 当预9.1版本的JDBC驱动程序不再使用时，移除该黑客。
	 */
	if (strcmp(*fc_newval, fc_canonical_name) != 0 &&
		strcmp(*fc_newval, "UNICODE") != 0)
	{
		free(*fc_newval);
		*fc_newval = strdup(fc_canonical_name);
		if (!*fc_newval)
			return false;
	}

	/*
	 * 在*extra中保存编码的ID，以供assign_client_encoding使用。
	 */
	*fc_extra = malloc(sizeof(int));
	if (!*fc_extra)
		return false;
	*((int *) *fc_extra) = fc_encoding;

	return true;
}

void assign_client_encoding(const char *fc_newval, void *fc_extra)
{
	int			fc_encoding = *((int *) fc_extra);

	/*
	 * 并行工作者将数据发送给领导，而不是客户。它们总是
	 * 使用数据库编码发送数据。
	 */
	if (IsParallelWorker())
	{
		/*
		 * 在并行工作者启动期间，我们希望接受领导的
		 * client_encoding设置，以便任何查看工作者中
		 * 值的人都会看到与领导中相同的值。
		 */
		if (InitializingParallelWorker)
			return;

		/*
		 * 除了在启动期间的更改，例如由于附加到函数定义的SET子句，
		 * 应该被拒绝，因为在工作者内部没有措施可以使其生效。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
				 errmsg("cannot change client_encoding during a parallel operation")));
	}

	/* 如果PrepareClientEncoding成功，我们不期望出现错误 */
	if (SetClientEncoding(fc_encoding) < 0)
		elog(LOG, "SetClientEncoding(%d) failed", fc_encoding);
}


/*
 * SET SESSION AUTHORIZATION
 */

typedef struct
{
	/* 这是SESSION AUTHORIZATION和ROLE的“extra”状态 */
	Oid			roleid;
	bool		is_superuser;
} role_auth_extra;

bool check_session_authorization(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	HeapTuple	fc_roleTup;
	Form_pg_authid fc_roleform;
	Oid			fc_roleid;
	bool		fc_is_superuser;
	role_auth_extra *fc_myextra;

	/* 对于默认值为NULL的boot_val不执行任何操作 */
	if (*fc_newval == NULL)
		return true;

	if (InitializingParallelWorker)
	{
		/*
		 * 在并行工作者初始化中，我们希望复制领导的
		 * 状态，即使它不再与目录匹配。ParallelWorkerMain
		 * 已经安装了正确的角色OID和超级用户状态。
		 */
		fc_roleid = GetSessionUserId();
		fc_is_superuser = GetSessionUserIsSuperuser();
	}
	else
	{
		if (!IsTransactionState())
		{
			/*
			 * 无法进行目录查找，因此失败。其结果是
			 * session_authorization不能在postgresql.conf中设置，这
			 * 看起来无论如何都是件好事，因此我们不努力去避免
			 * 它。
			 */
			return false;
		}

		/*
		 * 当source == PGC_S_TEST时，对于不存在的用户名或权限不足，
		 * 我们不会抛出严重错误，只会发出NOTICE。
		 * 请参阅guc.h中的注释。
		 */

		/* 查找用户名 */
		fc_roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*fc_newval));
		if (!HeapTupleIsValid(fc_roleTup))
		{
			if (fc_source == PGC_S_TEST)
			{
				ereport(NOTICE,
						(errcode(ERRCODE_UNDEFINED_OBJECT),
						 errmsg("role \"%s\" does not exist", *fc_newval)));
				return true;
			}
			GUC_check_errmsg("role \"%s\" does not exist", *fc_newval);
			return false;
		}

		fc_roleform = (Form_pg_authid) GETSTRUCT(fc_roleTup);
		fc_roleid = fc_roleform->oid;
		fc_is_superuser = fc_roleform->rolsuper;

		ReleaseSysCache(fc_roleTup);

		/*
		 * 只有超级用户可以将SESSION AUTHORIZATION设置为
		 * 自己以外的角色。注意在单个会话中多个SET的情况下，
		 * 原始身份验证用户是否为超级用户才是重要的。
		 */
		if (fc_roleid != GetAuthenticatedUserId() &&
			!GetAuthenticatedUserIsSuperuser())
		{
			if (fc_source == PGC_S_TEST)
			{
				ereport(NOTICE,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("permission will be denied to set session authorization \"%s\"",
								*fc_newval)));
				return true;
			}
			GUC_check_errcode(ERRCODE_INSUFFICIENT_PRIVILEGE);
			GUC_check_errmsg("permission denied to set session authorization \"%s\"",
							 *fc_newval);
			return false;
		}
	}

	/* 为assign_session_authorization设置“extra”结构以供使用 */
	fc_myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra));
	if (!fc_myextra)
		return false;
	fc_myextra->roleid = fc_roleid;
	fc_myextra->is_superuser = fc_is_superuser;
	*fc_extra = (void *) fc_myextra;

	return true;
}

void assign_session_authorization(const char *fc_newval, void *fc_extra)
{
	role_auth_extra *fc_myextra = (role_auth_extra *) fc_extra;

	/* 对于默认值为NULL的boot_val不执行任何操作 */
	if (!fc_myextra)
		return;

	SetSessionAuthorization(fc_myextra->roleid, fc_myextra->is_superuser);
}


/*
 * SET ROLE
 *
 * SQL规范要求 "SET ROLE NONE" 来取消角色，因此我们硬编码
 * "none" 的翻译为InvalidOid。否则，这与
 * SET SESSION AUTHORIZATION非常相似。
 */
extern char *role_string;		/* 在guc.c中 */

bool check_role(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	HeapTuple	fc_roleTup;
	Oid			fc_roleid;
	bool		fc_is_superuser;
	role_auth_extra *fc_myextra;
	Form_pg_authid fc_roleform;

	if (strcmp(*fc_newval, "none") == 0)
	{
		/* 硬编码的翻译 */
		fc_roleid = InvalidOid;
		fc_is_superuser = false;
	}
	else if (InitializingParallelWorker)
	{
		/*
		 * 在并行工作者初始化中，我们希望复制领导的
		 * 状态，即使它不再与目录匹配。ParallelWorkerMain
		 * 已经安装了正确的角色OID和超级用户状态。
		 */
		fc_roleid = GetCurrentRoleId();
		fc_is_superuser = session_auth_is_superuser;
	}
	else
	{
		if (!IsTransactionState())
		{
			/*
			 * 无法进行目录查找，因此失败。结果是
			 * 角色无法在 postgresql.conf 中设置，但这似乎无论如何都是件好事，因此我们不会很努力地去避免它。
			 */
			return false;
		}

		/*
		 * 当source == PGC_S_TEST时，对于不存在的用户名或权限不足，
		 * 我们不会抛出严重错误，只会发出NOTICE。
		 * 请参阅guc.h中的注释。
		 */

		/* 查找用户名 */
		fc_roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*fc_newval));
		if (!HeapTupleIsValid(fc_roleTup))
		{
			if (fc_source == PGC_S_TEST)
			{
				ereport(NOTICE,
						(errcode(ERRCODE_UNDEFINED_OBJECT),
						 errmsg("role \"%s\" does not exist", *fc_newval)));
				return true;
			}
			GUC_check_errmsg("role \"%s\" does not exist", *fc_newval);
			return false;
		}

		fc_roleform = (Form_pg_authid) GETSTRUCT(fc_roleTup);
		fc_roleid = fc_roleform->oid;
		fc_is_superuser = fc_roleform->rolsuper;

		ReleaseSysCache(fc_roleTup);

		/* 验证会话用户是否被允许成为此角色 */
		if (!is_member_of_role(GetSessionUserId(), fc_roleid))
		{
			if (fc_source == PGC_S_TEST)
			{
				ereport(NOTICE,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("permission will be denied to set role \"%s\"",
								*fc_newval)));
				return true;
			}
			GUC_check_errcode(ERRCODE_INSUFFICIENT_PRIVILEGE);
			GUC_check_errmsg("permission denied to set role \"%s\"",
							 *fc_newval);
			return false;
		}
	}

	/* 为 assign_role 设置 "extra" 结构以供使用 */
	fc_myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra));
	if (!fc_myextra)
		return false;
	fc_myextra->roleid = fc_roleid;
	fc_myextra->is_superuser = fc_is_superuser;
	*fc_extra = (void *) fc_myextra;

	return true;
}

void assign_role(const char *fc_newval, void *fc_extra)
{
	role_auth_extra *fc_myextra = (role_auth_extra *) fc_extra;

	SetCurrentRoleId(fc_myextra->roleid, fc_myextra->is_superuser);
}

const char * show_role(void)
{
	/*
	 * 检查 SET ROLE 是否处于活动状态；如果没有，则返回 "none"。这是一种绕过的方法，旨在处理 SET SESSION AUTHORIZATION 在逻辑上
	 * 将 SET ROLE 重置为 NONE，但我们无法从 assign_session_authorization 设置 GUC 角色变量（因为我们没有足够的信息
	 * 来调用 set_config_option）。
	 */
	if (!OidIsValid(GetCurrentRoleId()))
		return "none";

	/* 否则我们可以直接使用 GUC 字符串 */
	return role_string ? role_string : "none";
}
